#include "elevator.hpp"

using namespace std;

struct timeval g_time;

void passenger::start() {
    // wait for random amount of time
    int direc = (_in < _out)?ELEVATOR_UP:ELEVATOR_DOWN;
    _panel->press(direc);
}

void elevator::operate() {
    int elapsed_secs = 0;
    struct timeval now, last_updated;
    int last_fl = _cur_fl;

    while(_state != ELEVATOR_REPAIR) {
        now = g_time;
        elapsed_secs = getusecdiff(&last_updated, &now)/1000;

        //update current floor
        if (_state == ELEVATOR_UP) {
            _cur_fl += _velocity * elapsed_secs;
            if (_cur_fl > _thisBuilding.num_floors())
                _cur_fl = _thisBuilding.num_floors();
        } else if (_state == ELEVATOR_DOWN) {
            _cur_fl -= _velocity * elapsed_secs;
            if (_cur_fl < 1)
                _cur_fl = 1;
        }
        last_updated = now;

        // if requested to stop at the current floor,
        // stop and open door
        if (has_request(_cur_fl-1)) {
           open_door();
           // set direction to requested direc
           // if previously idle
           if (_state == ELEVATOR_IDLE) {
               _state = (_requests[_cur_fl-1].direc == ELEVATOR_UP)?
                   ELEVATOR_UP:ELEVATOR_DOWN;
           } 
        }

        // examine current list of passengers
        // let out passengers who exit at the current floor
        if (last_fl != _cur_fl) {
            for (it = passengers.begin(); it != passengers.end(); it++)
            {
                if ((*it).floor_exit() == _cur_fl) {
                    if (door_state = DOOR_CLOSED)
                        open_door();
                        // remove this passenger from the list
                    it = passengers.erase(it);
                }
            }
        }

        accept_passengers();
        close_door();

        // switch to idle state if elevator is empty
        if (passengers.empty()) {
            _state = ELEVATOR_IDLE;
        }

        // sleep and wait for signal of state update
        // pthread_cond_timedwait()
        event = pop_front(event_queue);
        switch (event) {
        case EVENT_UP:
            move_up();
            gettimeofday(&last_updated, NULL);
        case EVENT_DOWN:
            move_down();
            gettimeofday(&last_updated, NULL);
        case EVENT_STOP:
            _state = ELEVATOR_IDLE;
            gettimeofday(&last_updated, NULL);
        default: // other misc events
            ;
        }
    }
}

void elevator::schedule_request(int fl, int direc)
{
    assert(direc == ELEVATOR_UP || direc == ELEVATOR_DOWN);
    _requests[fl].value = true;
    _requests[fl].direc = direc;
    if (_state == ELEVATOR_IDLE) {
        if (_cur_fl < fl) {
            move_up();
        } else if (_cur_fl > fl) {
            move_down();
        }
    }
}

void elevator::accept_passengers() {
    list<passenger> *plist = thisBuilding._fl_queue + _cur_floor - 1;
    for (list<passenger>::iterator it = plist->begin();
         it != plist->end(); it++) {
        // add passengers of the right direction to the list
        if ((passengers.floor_exit() > _cur_fl &&
            _state == ELEVATOR_UP) ||
           (passengers.floor_exit() < _cur_fl &&
            _state == ELEVATOR_DOWN)) {
            passengers.push_back(*it);
        }
    }
    // clear the original list as passengers enter the elevator
    plist.clear();
}

// compute cost based on elevator's current fl and direction,
// and requested floor and direction
int elevator::compute_cost(int fl, int direc) 
{
    int cost = -1;

    // ignore inactive elevators
    if (_state == ELEVATOR_REPAIR)
        return -1;

    switch (_state) {
        case ELEVATOR_UP:
            if (direc != ELEVATOR_UP ||
                fl <= _cur_fl) {
                cost = -1;
            } else {
                cost = fl - _cur_fl;
            }
            break;
        case ELEVATOR_DOWN:
            if (direc != ELEVATOR_DOWN ||
                fl >= _cur_fl) {
                cost = -1;
            } else {
                cost = _cur_fl - fl;
            }
            break;
        case ELEVATOR_IDLE:
        default:
            cost = (_cur_fl >= fl)?(_cur_fl - fl):(fl - _cur_fl);
            break;
    }
    
    return cost;
}

// master function to update states of the building
void controller::run() {
    _running = true;

    while (_running) {
        // increase time by athe smallest slice
        g_time.tv_usec += TIMER_SLICE;
        if (g_time.tv_usec > 1000000) {
            g_time.tv_usec = g_time.tv_usec % 1000000;
            g_time.tv_sec ++;
        }

        // schedule new passenger arrivals
        for (int i = 0; i < _thisBuilding.num_floor(); i++)
        {
            list<passenger> *fl = thisBuilding._fl_queue + i;
            int num_new = rand() % MAX_NEW_PASSENGERS;
            for (j=0; j<num_new; j++) {
                passenger *p = new passenger;
                fl->push_back(*p);
                p->start();
            }
        }

        // schedule active requests for each floor
        for (int i = 0; i < _thisBuilding.num_floor(); i++) {
            panel *p = thisBuilding.get_panel(i+1);
            if (panel.up == true) {
                schedule(i+1, ELEVATOR_UP);
            }
            if (panel.down == true) {
                schedule(i+1, ELEVATOR_DOWN);
            }
        }

        ;
    }
}

// pick an elevator to serve a floor
void controller::schedule(int floor, int direc)
{
    int index = -1;
    int cost = -1;
   
    for (int i = 0; i < _thisBuilding.num_elevators(); i++)
    {
        int this_cost;
        elevator *e = _thisBuilding._elevators[i];

        if (e->_state == ELEVATOR_REPAIR)
            continue;
        this_cost = e->compute_cost(floor, direc);
        if (this_cost < 0) // TODO consider capacity of elevator
            continue;
        if (this_cost < cost || cost < 0) {
            // consider non-negative cost that is smaller
            cost = this_cost;
            index = i;
        }
    }

    elevator *e = _thisBuilding._elevators + index;
    e->schedule_request(floor, direc);
}

